[日本語Alexa] 強制的にステータスをCOMPLETEDに遷移させることで、複雑なダイアログモデルを作成してみる
1 はじめに
ダイアログモデルでは、必須のスロットを設定することで、必要なパラメータを非常に簡単に集めることができます。しかし、開発者コンソールからの設定だけでは、自由な設計にはちょっと限界があります。
例えば、「Aスロットは必須だけど、その内容によっては、Bスロットが必須になったり、Cスロットが必須になったり、はたまた、BやCは不要になったり」みたいな・・・
今回は、スロットの状態(内容)に応じて、コード側でこれを処理し、スロットを書き換えたり、不要なスロットにダミーを入れたりして、強制的にステータスをCOMPLETEDに遷移させることで、ちょっと複雑なダイアログモデルに挑戦してみたいと思います。
今回の題材は、レストランでの注文です。複雑と言っては大袈裟ですが、次のようなメニューがあったとします。
- 定食(Aセット・Bセット・Cセット)
- 蕎麦(暖かい・冷たい)
- うどん
メニューは、「定食」「蕎麦」及び、「うどん」の3種類です。そして定食はAセットからCセットの3種類から選べます。また、蕎麦は、温かいものと、冷たいものが選べます。
2 モデルの設計
(1) スロット
考えられるスロットは、次の3つでしょう。
- menu (メニューの種類)
- set (定食の種類)
- hotCold (温かい若しくは、冷たい)
(2) カスタムスロット
そして、それぞれのスロットのタイプに指定するためのカスタムスロットを作成します。
- LIST_OF_MENU 定食・蕎麦・うどん
- LIST_OF_SET Aセット・Bセット・Cセット
- LIST_OF_HOT_COLD 温かい・冷たい
(3) サンプル発話
これらを使用したサンプル発話を、とりあえず下記のようにしました。
- {menu}を下さい
- {hotCold}{menu}を下さい
- {set}を下さい
これで、「定食を下さい」「温かいおそばを下さい」「Aセットを下さい」みたいな発話を受け取ることが出来ます。
3 条件によるスロットの要否
「格納されたスロット」によって、更に必要なスロットと、もう必要ないスロットをマトリックスにしてみました。
発話例 | 格納されるスロット | 必要 | 不要 |
---|---|---|---|
定食を下さい | menu(定食) | set | hotCold |
A定食を下さい | set | menu,hotCold | |
温かいそばを下さい | menu(そば),hotCold | set | |
うどんを下さい | menu(うどん) | hotCold,set | |
そばを下さい | menu(そば) | hotCold | set |
上の表から、「格納されたスロット」を見て、必要なスロットがある場合は、それを要求し、不要なスロットには、ダミーを埋めてしまいます。全部のスロットが入れば、ダイアログモデルはCOMPLETEDになります。
- setが入っている場合 => menuに「定食」をセットして COMPLETED
- menuに「うどん」が入っている場合 => COMPLETED
- menuに「蕎麦」が入っている場合 hotColdが入ってなければ要求する
- menuに「蕎麦」が入っている場合 hotColdが入っていれば => COMPLETED
- menuに「定食」が入っている場合 setが入ってなければ要求する
- menuに「定食」が入っている場合 setが入っていれば => COMPLETED
これをコードにすると次のようになります。
'OrderIntent': function () { let intent = this.event.request.intent; if (this.event.request.dialogState !== 'COMPLETED') { if (intent.slots.set.value) { // セットが指定されてい場合 intent.slots.menu.value = '定食'; // メニューに「定食」を設定 intent.slots.hotCold.value = '?';// 不要スロットの無効化 } else if (intent.slots.menu.value) { // メニューが設定されている場合 let menu = intent.slots.menu.resolutions.resolutionsPerAuthority[0].values[0].value.id; if(menu == 'udon') { // メニューに「うどん」が設定されている場合 intent.slots.hotCold.value = '?';// 不要スロットの無効化 intent.slots.set.value = '?';// 不要スロットの無効化 } else if(menu == 'soba') {// メニューに「蕎麦」が設定されている場合 intent.slots.set.value = '?';// 不要スロットの無効化 if (!intent.slots.hotCold.value) { let speechOutput = '温かいお蕎麦と、冷たいお蕎麦のどちらになさいますか?'; this.emit(':confirmSlot','hotCold', speechOutput, speechOutput); return; } } else { // メニューに「定食」が設定されている場合 intent.slots.hotCold.value = '?'; // 不要スロットの無効化 if (!intent.slots.set.value) { let speechOutput = 'Aセット、Bセット、Cセットの3種類から選べます。どれになさいますか?'; this.emit(':confirmSlot','set', speechOutput, speechOutput); return; } } } this.emit(':delegate', intent); } else { let answer = ''; let menu = ''; if(intent.slots.menu.resolutions){ menu = intent.slots.menu.resolutions.resolutionsPerAuthority[0].values[0].value.id; } else { // 強制的に「定食」を入れた場合、resolutionsが無い menu = "setMenu"; } if(menu == 'udon') { answer = 'おうどん'; } else if(menu == 'soba') { let hotCold = intent.slots.hotCold.resolutions.resolutionsPerAuthority[0].values[0].value.id; if(hotCold=='hot') { answer = '温かいお蕎麦'; } else { answer = '冷たいお蕎麦'; } } else { //setMenu let set = intent.slots.set.resolutions.resolutionsPerAuthority[0].values[0].value.id; if(set=='a_set') { answer = 'Aセット'; }else if(set=='b_set') { answer = 'Bセット'; } else { answer = 'Cセット'; } } this.emit(':tell',answer + 'を用意させて頂きます。' ); } }
テストしている様子です。
4 最後に
今回は、強制的にスロットを埋めて、ステータスをCOMPLETEDに遷移させることで、必須スロットが、A and B or C のような複雑なダイアログ管理が可能になることを確認してみました。
うまく組み合わせると、かなり自由にオーダーが取得できるのでは無いでしょうか。